1 /*
2  * Collie - An asynchronous event-driven network framework using Dlang development
3  *
4  * Copyright (C) 2015-2017  Shanghai Putao Technology Co., Ltd 
5  *
6  * Developer: putao's Dlang team
7  *
8  * Licensed under the Apache-2.0 License.
9  *
10  */
11 module collie.codec.http.server.httpform;
12 
13 import kiss.container.ByteBuffer;
14 import std.array;
15 import std.string;
16 import std.exception;
17 import std.algorithm.searching : canFind, countUntil;
18 import kiss.logger;
19 import collie.utils.string;
20 import kiss.container.Vector;
21 import std.experimental.allocator.mallocator;
22 import std.uri;
23 
24 class HTTPFormException : Exception
25 {
26 	mixin basicExceptionCtors;
27 }
28 
29 
30 alias HttpForm = HTTPForm;
31 
32 class HTTPForm
33 {
34 	import std.experimental.allocator.mallocator;
35 	alias TBuffer = Vector!(ubyte,Mallocator,false);
36 	alias StringArray = string[];
37 	enum ubyte[2] ENDMYITLFORM = ['-','-']; 
38 	enum ubyte[2] LRLN = ['\r','\n']; 
39 
40 	final class FormFile
41 	{
42 		@property fileName() const {return _fileName;}
43 		@property contentType() const {return _contentType;}
44 		@property fileSize()const {return _length;} 
45 		void read(size_t size,scope void delegate(in ubyte[] data) cback) 
46 		{
47 			size = size > _length ? _length : size;
48 			_body.rest(_startSize);
49 			_body.read(size,cback);
50 		}
51 	private : 
52 		Buffer _body;
53 		size_t _startSize = 0;
54 		size_t _length = 0;
55 		string _fileName;
56 		string _contentType;
57 		this(){}
58 	}
59 	
60 	this(string contype,  Buffer body_)
61 	{
62 		// logDebug("contype is : ", contype);
63 		if (canFind(contype, "multipart/form-data"))
64 		{
65 			string strBoundary;
66 			splitNameValue(contype,';','=',(string key,string value){
67 					if(isSameIngnoreLowUp(strip(key),"boundary")) {
68 						strBoundary = value.idup;
69 						return false;
70 					}
71 					return true;
72 				});
73 			logDebug("strBoundary : ", strBoundary);
74 			if (strBoundary.length > 0)
75 			{
76 				if(strBoundary[0] == '\"')
77 					strBoundary = strBoundary[1..strBoundary.length -1];
78 				readMultiFrom(strBoundary, body_);
79 			}
80 		}
81 		else if (canFind(contype, "application/x-www-form-urlencoded"))
82 		{
83 			readXform(body_);
84 		}
85 		else
86 		{
87 			_vaild = false;
88 		}
89 		body_.rest();
90 	}
91 	
92 	@property bool isVaild() const
93 	{
94 		return _vaild;
95 	}
96 	
97 	/**
98      * Request body parameters ($_POST).
99      *
100      */
101 	@property string[string] formData()
102 	{
103 		return _formData;
104 	}	
105 	
106 	@property void formData(string[string] v)
107 	{
108 		_formData = v;
109 	}
110 
111 	protected string[string] _formData;
112 
113 	
114 	@property StringArray[string] formMap()
115 	{
116 		return _forms;
117 	}
118 	
119 	
120 	@property FormFile[string] fileMap()
121 	{
122 		return _files;
123 	}
124 
125 	string[] fileKeys()
126 	{
127 		return _files.keys();
128 	}
129 	
130 	string getFromValue(string key)
131 	{
132 		StringArray aty = _forms.get(key, StringArray.init);
133 		if(aty.length == 0)
134 			return "";
135 		else
136 			return aty[0];
137 	}
138 	
139 	StringArray getFromValueArray(string key)
140 	{
141 		StringArray aty;
142 		return _forms.get(key, aty);
143 	}
144 	
145 	FormFile getFileValue(string key)
146 	{
147 		return _files.get(key, null);
148 	}
149 	
150 protected:
151 	void readXform(Buffer buffer)
152 	{
153 		buffer.rest(0);
154 		ubyte[] str;
155 		buffer.readAll((in ubyte[] data){
156 				str ~= data;
157 			});
158 		splitNameValue(cast(string)str,'&','=',(string key, string value){
159 				value = value.replace("+","%20");
160 				string v = decodeComponent(value);
161 				logDebugf("recv: %s=%s, decoded:%s",key, value, v);
162 				string k = key.idup;
163 				_formData[k] = v;
164 				if(value.length > 0)
165 					_forms[k] ~= v;
166 				else
167 					_forms[k] ~= "";
168 				return true;
169 			});
170 	}
171 	
172 	void readMultiFrom(string brand, Buffer buffer)
173 	{
174 		// buffer.readAll((in ubyte[] data){
175 		// 		logDebug("data is : ", cast(string)data);
176 		// 	});
177 		// logDebug(".................");
178 		buffer.rest(0);
179 		string brony = "--";
180 		brony ~= brand;
181 		string str;
182 		auto buf = Vector!(ubyte,Mallocator)();
183 		do{
184 			//Appender!(ubyte[]) buf = appender!(ubyte[]);
185 			buf.clear();
186 			buffer.readLine((in ubyte[] data){
187 					logDebug("data is : ", cast(string)data);
188 					buf.insertBack(data);
189 					//buf.put(data);
190 				});
191 			auto sttr = cast(string)buf.data();
192 			str = sttr.strip;
193 			if(str.length == 0){
194 				continue;
195 			} else if(str == brony){
196 				break;
197 			}  else {
198 				return;
199 			}
200 		} while(true);
201 		logDebug("read to  : ", buffer.readPos);
202 		logDebug("brony length  : ", brony.length);
203 		brony = "\r\n" ~ brony;
204 		bool run;
205 		do
206 		{
207 			run = readMultiftomPart(buffer, cast(ubyte[]) brony);
208 		}
209 		while (run);
210 	}
211 	
212 	bool readMultiftomPart(Buffer buffer, ubyte[] boundary)
213 	{
214 		auto buf = Vector!(ubyte,Mallocator)();
215 		string cd;
216 		string cType;
217 		do {
218 			buf.clear();
219 			buffer.readLine((in ubyte[] data){
220 					buf.insertBack(data);
221 				});
222 			auto line = buf.data();
223 			logDebug(cast(string)line);
224 			if(line.length == 0)
225 				break;
226 			auto pos = countUntil(line, cast(ubyte)':') ; //  (cast(string) line).indexOf(":");
227 			++pos;
228 			if (pos <= 0 || pos >= line.length)
229 				continue;
230 			string key = cast(string)(line[0 .. pos - 1]);
231 			if(isSameIngnoreLowUp(strip(key),"content-disposition")){
232 				line = line[pos .. $];
233 				pos = countUntil(line, cast(ubyte)';');
234 				++pos;
235 				if (pos <= 0 || pos >= line.length)
236 					continue;
237 				cd = cast(string)line[pos + 1 .. $].idup;
238 			} else if(isSameIngnoreLowUp(strip(key),"content-type")){
239 				cType = strip((cast(string)(line[pos + 1 .. $]))).idup;
240 			}
241 		} while(true);
242 		if (cd.length == 0)
243 			return false;
244 		
245 		string name;
246 		string filename;
247 		logDebug("cd ====       ", cd);
248 		splitNameValue(cd, ';' , '=' , (string key, string value){
249 			logDebug("key :  ", key, "   value: ", value);
250 			string tkey = strip(key);
251 			//string tvalue = strip(value);
252 			string handleValue(string rv){
253 				if(rv.length > 0) 
254 					if(rv[0] == '\"') rv = rv[1 .. $];
255 				if(rv.length > 0) 
256 					if(rv[$-1] == '\"') rv = rv[0 .. $ - 1];
257 				return rv.idup;
258 			}
259 			switch(tkey){
260 				case "name":
261 					name = handleValue(strip(value));
262 				break;
263 				case "filename":
264 					filename = handleValue(strip(value));
265 				break;
266 				default:
267 				break;
268 			}
269 			return true;
270 		});
271 		if (filename.length > 0)
272 		{
273 			import std.array;
274 			FormFile fp = new FormFile;
275 			fp._fileName = filename;
276 			fp._contentType = cType;
277 			fp._startSize = buffer.readPos();
278 			fp._body = buffer;
279 			buffer.readUtil(boundary,(in ubyte[] rdata) {
280 					fp._length += rdata.length;
281 				});
282 			_files[name] = fp;
283 		}
284 		else
285 		{
286 			import std.array;
287 			auto value = appender!(string)();
288 			buffer.readUtil(boundary, delegate(in ubyte[] rdata) {
289 					value.put(cast(string) rdata);
290 				});
291 			string stdr = value.data;
292 			logDebug("name == ", name);
293 			logDebug("value == ", stdr);
294 			
295 			_forms[name] ~= stdr;
296 			logDebug("form : ", _forms);
297 			
298 		}
299 		ubyte[2] ub;
300 		bool frist = true;
301 		buffer.read(2,(in ubyte[] dt){
302 				switch(dt.length){
303 					case 2:
304 						ub[] = dt[];
305 						break;
306 					case 1:{
307 						if(frist){
308 							ub[0] = dt[0];
309 							frist = false;
310 						} else {
311 							ub[1] = dt[0];
312 						}
313 					}break;
314 					default:
315 						break;
316 				}
317 			});
318 		if (ub == ENDMYITLFORM)
319 		{
320 			return false;
321 		}
322 		enforce!HTTPFormException(ub == LRLN, "showed be \\r\\n");
323 		return true;
324 	}
325 
326 private:
327 	bool _vaild = true;
328 	StringArray[string] _forms;
329 	FormFile[string] _files;
330 }